34% of your Claude Code tokens go to hook fights. Here's the 3-line fix.
You installed 38 agents. You added Superpowers, claude-mem, AgentShield, ECC. Two PostToolUse hooks edit the same file. Three agents claim the same matcher. Your token bill is up 40% and Claude keeps "forgetting" your CLAUDE.md.
You think the model is dumb. It isn't. Your agents are fighting each other.
I spent 11 days putting an HTTP proxy between Claude Code and the Anthropic API, logging every hook execution across 14 different setups.
The pattern is consistent. The fix is three lines.
Here's what's actually happening, and how to stop it tonight.
The proof
I instrumented 14 Claude Code setups for 11 days. All of them used 3+ plugins from the official marketplace. Half had ECC installed. 
All of them ran into one of three failure modes:
PostToolUse cascade.
A hook modifies a file, which fires another PostToolUse, which fires another. One developer's setup ran 7 chained PostToolUse events on a single Edit. 7x token cost. He thought Claude was "running slow."
Matcher overlap.
Two agents both register matcher: "Edit|Write" for code review. They run in series. They produce contradictory recommendations. Claude tries to obey both. Code quality drops.
Hook injection conflicts.
UserPromptSubmit hook from skill A injects context. Another from skill B injects different context. Claude reads both as "user said X" and gets confused about what you actually asked.
Average token waste across the 14 setups: 34% of every session, gone before Claude generated a single useful token.
Worst case: one developer was burning his Claude Max 20x weekly limit by Wednesday. After the fix below, he was hitting limits Sunday evening. Three full days of additional capacity from changing 3 lines in settings.json.
How I measured this
The HTTP proxy sat between Claude Code and the Anthropic API, logging every request and response with timestamps, token counts, and the full hook execution trace. Each setup ran for ~11 days of normal work: real projects, real edits, real prompts. Not synthetic benchmarks.
For each session, I counted:
Total tokens billed
Tokens spent on hook-injected context (UserPromptSubmit, SessionStart)
Tokens spent on PostToolUse cascade re-fires (same hook, same edit, fired more than once)
Tokens spent on contradictory subagent outputs that Claude then had to reconcile
The "34% wasted" number is the sum of categories 2-4 divided by the total. Wide variance across the 14 setups: cleanest setup wasted 18%, dirtiest wasted 52%. Median 34%. Every single setup lost more than 15% to fights. There were no clean rooms.
The fix below targets the largest of those buckets, cascades, because it's both the easiest to fix and the one that compounds fastest as you install more plugins.
Why this happens
Claude Code v2.1.116+ supports 26 hook lifecycle events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop, SubagentStart, SubagentStop, PreCompact, PostToolUseFailure, PermissionRequest, PermissionDenied, Notification, TaskCreated, CwdChanged, FileChanged, and more.
Each one fires when the matching event happens. Each plugin can register hooks on any of them.
There is no global ordering rule between plugins.
When plugin A registers a PostToolUse hook on Edit|Write and plugin B registers a PostToolUse hook on .*, both fire on every Edit. Order depends on:
Personal-level hooks (~/.claude/settings.json) load first
Project-level hooks (.claude/settings.json) load second
Plugin-level hooks load in plugin install order
Within the same level, alphabetical by plugin name
Most people don't know this. So they install 5 plugins and assume "Claude Code figures it out." It doesn't.
It runs all of them, in the order they happened to load, with no awareness that 3 of them are doing the same job.
The other thing nobody talks about: a PostToolUse hook that modifies a file triggers another PostToolUse event.
This is the #1 cause of the cascade problem. If you have a Prettier hook on Edit|Write and an ESLint hook on Edit|Write, the Prettier write triggers the ESLint hook, which writes again, which triggers Prettier again. Both are using your tokens to run.
The 3 conflicts that destroy 90% of setups
Conflict #1 — Two formatters fighting
The classic. You have Prettier and Biome (or ESLint --fix). Both registered on PostToolUse: Edit|Write. Both rewrite the same file. Each rewrite triggers the other.
How to spot it: look at hook execution count after a single Edit. If you see PostToolUse fired 3+ times in one tool call, you have a cascade.
# Diagnostic: run this in a project after one Claude edit
grep "PostToolUse" ~/.claude/logs/$(ls -t ~/.claude/logs/ | head -1) | wc -l
If the result is more than 2 per Edit, you're in cascade.
What it costs. A clean Edit on a 200-line file costs ~3,200 tokens (read file -> edit -> write). With a 3-fire cascade, the same edit costs ~9,600 tokens. Every formatter run re-tokenizes the modified file as part of the next hook's context. On a 50-edit refactor, that's the difference between $0.40 and $1.20 in API spend, or for Max users, half a day of weekly limit gone to formatting fights.
Conflict #2 — Multiple "code reviewers"
You installed Superpowers (which runs a reviewer subagent). You also installed a separate code reviewer plugin. ECC adds its own code-reviewer.md agent. All three trigger on PR creation or after Edit.
Result: Claude gets three contradictory reviews. It tries to merge them. The merged response is inconsistent. You think "the model is bad today." It isn't. You have three reviewers fighting.
How to spot it: after a code change, ask Claude "which agents reviewed this?". If the answer is more than one, decide which one you actually trust and disable the rest.
Conflict #3 — UserPromptSubmit injection wars
This is the silent killer. UserPromptSubmit hooks let plugins inject context into every prompt you send. Useful for things like "current branch name" or "last commit message."
Some plugins inject a lot of context. ECC injects continuous-learning instincts. claude-mem injects memory summaries. Skill Each prompt now has 8KB of pre-pended context before your actual question.
Result: Claude's response degrades because the signal-to-noise ratio collapsed. Your CLAUDE.md instructions are buried under three layers of plugin chatter. The behavior people blame on "Claude getting dumber" is often this.
Concrete example. I had ECC, claude-mem, and a custom git-context skill all injecting on UserPromptSubmit. My prompts looked clean ("fix the bug in auth.js line 42"). Claude was receiving 6,400 tokens of injection before my question. Average response quality measured against my own CLAUDE.md rules dropped from 78% rule-compliance to 41%. After disabling claude-mem injection (kept its retrieval, killed the auto-inject) compliance went back to 76%. Same model. Same prompts. Different signal to noise.
How to spot it: in Claude Code, run /transcript after a session. If the actual user-prompt-token-count is lower than the total-prompt-token-count by 3x or more, you have heavy injection. Pick which plugins genuinely earn their slot. Disable the rest.
The 3-line fix
Open ~/.claude/settings.json (or your project .claude/settings.json). Add:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "[ -f .claude/.hook-lock ] && exit 0; touch .claude/.hook-lock; npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>/dev/null; rm -f .claude/.hook-lock; exit 0"
          }
        ]
      }
    ]
  }
}
What this does:
Lock file .claude/.hook-lock. First PostToolUse creates it. Any nested PostToolUse triggered by the formatter sees the lock and exits immediately. Cascade dies.
Single formatter path. Pick one (Prettier OR Biome OR ESLint --fix). Pick. One. Don't hedge.
exit 0 always. Never let a formatter failure cascade into Claude retrying the edit. If Prettier fails on a file, that's information for you, not for the agent.
Three lines. The [ -f .claude/.hook-lock ] && exit 0 check is the entire fix for cascades.
Add to .gitignore:
.claude/.hook-lock
Why a lock file (and not a debounce or a timeout)
The obvious alternatives don't work:
Debounce by timestamp.
 "Skip if last fire was less than N seconds ago." Fragile. N too short and you lose legitimate edits, too long and cascades still fire. Also depends on clock sync and breaks in CI.
async: true on the hook.
 Removes blocking but creates race conditions. Two hooks writing the same file in parallel, last-write-wins, silent corruption. Worse than the cascade.
Hook-level if checks ("only run if file changed since last run").
Works for one hook but doesn't compose. Plugin authors don't know what other plugins are installed alongside theirs.
Disable hooks by plugin name.
 Brittle. Survives until the next plugin install or rename.
The lock file works because it's stateful, single-source-of-truth, and atomic at the filesystem level . The first hook in a chain creates it. Every subsequent fire, whether from the same plugin or a different one, sees the lock and exits. The file is removed at the end of the formatting pass.
Cross plugin, cross tool, no coordination needed.
Auditing your existing setup
Before adding the fix, you need to know what you have. Here's a 30-second audit:
# List all hooks across personal + project levels
cat ~/.claude/settings.json 2>/dev/null | jq '.hooks // {}'
cat .claude/settings.json 2>/dev/null | jq '.hooks // {}'

# List all installed plugins
claude /plugin list

# Find duplicate matchers (same event + same matcher = fight)
cat ~/.claude/settings.json .claude/settings.json 2>/dev/null \
  | jq -s '[.[].hooks // {}] | add | to_entries[] | {event: .key, matchers: [.value[].matcher]} | select(.matchers | length > 1)'
If you see two entries with the same event and overlapping matchers, you have a fight. Pick the winner. Delete the loser. Rerun the audit.
For ECC users specifically: npx ecc-agentshield scan will surface most of these conflicts in its audit pass.
When to use Agent Teams instead
Claude Code v2.1.32+ has Agent Teams as an experimental feature (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1). This is the official answer to the multi-agent problem. It requires Opus 4.6 , which means it's significantly more expensive per token than running hooks. But the cost-per-task can be lower because teammates work in parallel and don't pollute each other's context.
Agent Teams give you:
A team lead that coordinates all teammates
Each teammate runs in its own context window (no pollution)
Shared task list for coordination
Direct messaging vs broadcast (cost aware)
This is genuinely the right solution for parallel work: refactoring 3 modules at once, running tests in isolation, code review in parallel. It is not a replacement for hooks. Hooks are deterministic enforcement (always run Prettier on save). Teams are AI-coordinated parallel work (refactor X while reviewing Y).
The trade-off: Teams burn Opus 4.6 tokens at 5x the rate of a Sonnet 4.6 single-session. A 3-teammate refactor that finishes in 12 minutes might cost the same as a 45-minute Sonnet session. Faster, not cheaper. Use the speed when it matters (a customer is waiting, you're trying to ship before EOD), not when it doesn't.
Use both. Hooks for "must always run." Teams for "complex parallel tasks that benefits from speed."
What didn't work
Things I tried before settling on the lock-file fix:
Disabling all hooks.
 Fixed the cascade but lost the value of formatters/linters. Not viable for production.
async: true on hooks.
 Works for Stop hooks but creates race conditions on PostToolUse. Files get written mid-format. Don't.
Plugin-level disabling via ECC_DISABLED_HOOKS.
 Works only if you use ECC, and you still have to know which hooks conflict. Doesn't generalize.
Reducing hooks to one per event.
 Clean but you lose features. The lock file gives you both.
Hook ordering manipulation by renaming plugins alphabetically.
 Works but fragile. Survives until the next plugin installs.
The lock file approach won because it's plugin-agnostic, survives reinstalls, and handles the cascade root cause directly.
Symptoms that mean you have this problem right now
Run this checklist on your current setup:
Hit Claude Max usage limits more than 2 days before the weekly reset
"Claude got dumber" feeling that started after installing 2-3 plugins
CLAUDE.md instructions being ignored that worked fine 2 weeks ago
Edit operations that take noticeably longer than they used to
Two different "code review" outputs in one response
Conflicting suggestions from agents in the same session
PostToolUse fires more than once for a single Edit
"permission denied" or hook errors appearing in transcript
If you checked 3+, you have agents fighting. Apply the fix.
Full settings.json (copy-paste ready)
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|DROP TABLE|--force|sudo' && { echo '{\"message\":\"Blocked: dangerous command\"}'; exit 2; } || exit 0"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "[ -f .claude/.hook-lock ] && exit 0; touch .claude/.hook-lock; npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>/dev/null; rm -f .claude/.hook-lock; exit 0"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date): session complete\" >> ~/.claude/work.log",
            "async": true
          }
        ]
      }
    ]
  }
}
This config: blocks dangerous bash, formats every edit exactly once, logs sessions for later analysis. No fights.
The mental model
Stop thinking about Claude Code as one assistant. Start thinking of it as a small distributed system.
Hooks = deterministic infrastructure. Always runs. Doesn't think.
Skills = specialized procedures. Loaded on demand by the model. Think of them as well-documented functions.
Subagents = isolated workers with their own context window. Use for parallel work or context isolation.
Agent Teams (experimental) = coordination layer over subagents. Use for complex parallel features.
CLAUDE.md = the soft contract. Followed about 80% of the time. Use for style and convention, not safety.
The fights happen when you treat all five as the same thing. They're not. Each layer has a role.
For things that must happen every time: hooks (with the lock file). For things that need specialized procedures : skills . For things that need isolated workspace : subagents . For complex parallel work : Agent Teams .
Stop layering 5 hooks for the same job. Pick the right tool. The fighting stops.
One more frame: race conditions, not bad behavior
If you've worked on distributed systems, the cascade pattern is a familiar one: two processes, no shared lock, both writing to the same resource, both triggering each other. The solution there is the same as it is here — a mutex. The lock file is a mutex.
The reason this took so long for the Claude Code ecosystem to figure out: most plugin authors are writing in isolation. They test their plugin alone, on a clean install, and ship. Nobody is responsible for the interaction between plugins. There's no global ordering rule because nobody owns the global. Each plugin assumes it's the only one running on Edit|Write.
Agent Teams is Anthropic's first attempt at a coordination layer. The lock file is what you can do tonight, without waiting for the ecosystem to catch up. Both will exist for a long time: one for "coordinated parallel work," one for "deterministic guarantee no matter who else is in the room."
Closing
The Claude Code ecosystem grew faster than anyone expected. Marketplaces went from 16 official skills in late 2025 to 1.2M+ community skills indexed across marketplaces by April 2026. The plumbing didn't catch up. Most setups installed in the last 6 months have at least one of the conflicts above, and most of them blame the model.
This is the third pattern I've documented in the same family. The first was where your tokens actually go (73% wasted on 9 invisible patterns). The second was the rules that govern what Claude does once the tokens land (Karpathy's 4 + my 8). This one is the layer underneath both: the wiring between the plugins themselves. Each of the three layers has its own kind of failure mode. Most setups have at least one failing.
Your tokens are real money. Your time is real time. Three lines of settings.json and an evening of audit work returns 30%+ of both.
Audit your setup tonight. Add the lock file. Pick one formatter. Run for a week. Check your usage